#include "FluidScene.h"

#include <iostream>

#include <NxPlaneShapeDesc.h>
#include <PhysXLoader.h>

#include "OpenGLExtensionLoader.h"

FluidScene::FluidScene()
{
    this->differentParticles[PARTICLES_CONTAINER_WATER] = ParticlesContainer();
    this->differentParticles[PARTICLES_CONTAINER_RAYFLUID] = ParticlesContainer();

    this->fluidTexture = NULL;
    this->groundTexture = NULL;
    this->modelLog = NULL;
    this->model = NULL;
    this->modelActor = NULL;
    this->groundActor = NULL;
}

FluidScene::~FluidScene()
{
    CleanScene();
}

void FluidScene::PrepareScene(NxScene *scene)
{
    PhysxLogic::cam = new Camera(NxVec3(25.0f, 8.0f, 4.0f), NxVec3(-0.7f, -0.2f, 0.07f), (float)glutGet(GLUT_WINDOW_WIDTH)/(float)glutGet(GLUT_WINDOW_HEIGHT));

    //default material
    NxMaterial *defaultMaterial = scene->getMaterialFromIndex(0);
    defaultMaterial->setRestitution(0.5f);
    defaultMaterial->setStaticFriction(0.2f);
    defaultMaterial->setDynamicFriction(0.2f);

    //create plane
    NxPlaneShapeDesc planeDesc;
    planeDesc.shapeFlags |= NX_SF_FLUID_DRAIN;
    NxActorDesc groundActorDesc;	
    groundActorDesc.shapes.pushBack(&planeDesc);
    this->groundActor = scene->createActor(groundActorDesc);

    //load textures
    this->fluidTexture = new DevilTexture();
    this->fluidTexture->Load("..\\res\\tex\\particle.png");

    this->groundTexture = new DevilTexture();
    this->groundTexture->Load("..\\res\\tex\\ground.png");

    //creat pyramid boxes
    CreateBoxPyramid(7, NxVec3(0.5f, 0.5f, 0.5f), NxVec3(0,0,11));

    //load OpenGL Extension
    LoadPointParameterExtension();

    //create and load model
    this->modelLog = new Log();
    this->model = new Model(this->modelLog);
    this->model->Load("..\\res\\brunnen.obj", 0.7f, &PhysxLogic::physicsScene->getPhysicsSDK(), ModelBoundingVolume::TRIANGLE_MESH_SHAPE);

    NxActorDesc modelActorDesc;
    modelActorDesc.density = 1.0f;
    modelActorDesc.globalPose.t = NxVec3(0,0,0);
    modelActorDesc.globalPose.M = PhysxLogic::CalcOrientationMatrix(NxVec3(0,0,0));

    Modelpart **modelparts = model->GetModelparts();
    for(int i = 0; i < model->GetNbModelparts(); ++i)
    {
        ModelBoundingVolume *bv = modelparts[i]->GetBoundingVolume();
        if(NxBoxShapeDesc *shDesc = bv->IsBoxShape()) {
            modelActorDesc.shapes.pushBack(shDesc);
        } else if(NxConvexShapeDesc *shDesc = bv->IsConvexShape())	{
            modelActorDesc.shapes.pushBack(shDesc);
        } else if(NxTriangleMeshShapeDesc *shDesc = bv->IsTriangleMeshShape()) {
            modelActorDesc.shapes.pushBack(shDesc);
        }
    }
    this->modelActor = PhysxLogic::physicsScene->createActor(modelActorDesc);
}

void FluidScene::CleanScene()
{
    if(this->fluidTexture != NULL)
    {
        delete this->fluidTexture;
        this->fluidTexture = NULL;
    }
    if(this->groundTexture != NULL)
    {
        delete this->groundTexture;
        this->groundTexture = NULL;
    }
    if(this->model != NULL)
    {
        delete this->model;
        this->model = NULL;
    }
    if(this->modelLog != NULL)
    {
        delete this->modelLog;
        this->modelLog = NULL;
    }
    if(this->modelActor != NULL)
    {
        PhysxLogic::physicsScene->releaseActor(*(this->modelActor));
        this->modelActor = NULL;
    }
    if(this->groundActor != NULL)
    {
        PhysxLogic::physicsScene->releaseActor(*(this->groundActor));
        this->groundActor = NULL;
    }

    for(int i = 0; i < PARTICLES_CONTAINER_COUNT; ++i)
    {
        ParticlesContainer &pc = this->differentParticles[i];
        if(pc.fluid != NULL && pc.emitter != NULL)
        {
            pc.fluid->releaseEmitter(*pc.emitter);
            pc.emitter = NULL;
            pc.fluid->removeAllParticles();
            PhysxLogic::physicsScene->releaseFluid(*pc.fluid);
            pc.fluid = NULL;
        }        
        pc.ResetData();        
    }  

    delete PhysxLogic::cam;
}

void FluidScene::CreateBoxPyramid(int boxAmount, const NxVec3 &boxSize, const NxVec3 &position)
{
    NxVec3 startPos(position.x, position.y + boxSize.y, position.z - boxAmount*boxSize.z);
    NxVec3 boxPosition(0.0f, 0.0f, 0.0f);
    for(int h = 0; h < boxAmount; ++h)
    {
        for(int z = 0; z < boxAmount-h; ++z)
        {
            boxPosition.set(startPos.x, startPos.y + h*2.0f*boxSize.y, startPos.z + h*boxSize.z + z*2.0f*boxSize.z);
            NxActor *box = PhysxLogic::CreateBox(boxPosition, boxSize, NxVec3(0,0,0), false, 5.0f);
            box->getShapes()[0]->setFlag(NX_SF_FLUID_TWOWAY, true);
        }
    }
}

void FluidScene::CreateWaterFountain(int maxParticles, const NxVec3 &position)
{
    ParticlesContainer *water = &this->differentParticles[PARTICLES_CONTAINER_WATER];
	
    //allocate buffer for particle
    water->name = "water";
    water->nbParticles = new NxU32(0);
    water->particlesBuffer = new Particle[maxParticles];
    water->particlesColor = NxVec3(0.25f, 0.75f, 1.0f);
    water->particlesColorAlpha = -1.0f;

    water->particleTexture = this->fluidTexture;

    NxParticleData particlesData;
    particlesData.numParticlesPtr = water->nbParticles;
    particlesData.bufferId = &water->particlesBuffer[0].id;
    particlesData.bufferIdByteStride = sizeof(Particle);
    particlesData.bufferPos = &water->particlesBuffer[0].position.x;
    particlesData.bufferPosByteStride = sizeof(Particle);
    particlesData.bufferVel = &water->particlesBuffer[0].velocity.x;
    particlesData.bufferVelByteStride = sizeof(Particle);
    particlesData.bufferLife = &water->particlesBuffer[0].lifetime;
    particlesData.bufferLifeByteStride = sizeof(Particle);
    particlesData.bufferDensity = &water->particlesBuffer[0].density;
    particlesData.bufferDensityByteStride = sizeof(Particle);
    particlesData.bufferCollisionNormal = &water->particlesBuffer[0].collisionNormal.x;
    particlesData.bufferCollisionNormalByteStride = sizeof(Particle);

    //create fluid
    NxFluidDesc fluidDesc;
    fluidDesc.simulationMethod = NX_F_SPH;
    fluidDesc.initialParticleData = particlesData;
    fluidDesc.particlesWriteData = particlesData;
    fluidDesc.kernelRadiusMultiplier = 2.3f;
    fluidDesc.restParticlesPerMeter = 10.0f;
    fluidDesc.stiffness = 200.0f;
    fluidDesc.viscosity = 20.0f;
    fluidDesc.restDensity = 1000.0f;
    fluidDesc.damping = 5.0f;    
    if(PhysxLogic::IsHardwareAccelerated() == false)
        fluidDesc.flags &= ~NX_FF_HARDWARE;

    water->fluid = PhysxLogic::physicsScene->createFluid(fluidDesc);

    //create emitter
    NxFluidEmitterDesc emitterDesc;
    emitterDesc.maxParticles = maxParticles;
    emitterDesc.dimensionX = 0.1f;
    emitterDesc.dimensionY = 0.1f;
    emitterDesc.shape = NX_FE_ELLIPSE;
    emitterDesc.type = NX_FE_CONSTANT_PRESSURE;
    emitterDesc.fluidVelocityMagnitude = 10.0f;
    emitterDesc.particleLifetime = 0.0f;    

    emitterDesc.relPose.t = position;
    emitterDesc.relPose.M = PhysxLogic::CalcOrientationMatrix(NxVec3(-90,0,0));

    water->emitter = water->fluid->createEmitter(emitterDesc);
}

void FluidScene::CreateFluidRay(int maxParticles, int emitterRate, const NxMat34 &pose)
{
    ParticlesContainer *rayFluid = &this->differentParticles[PARTICLES_CONTAINER_RAYFLUID];

    //allocate buffer for particle
    rayFluid->nbParticles = new NxU32(0);
    rayFluid->particlesBuffer = new Particle[maxParticles];
    rayFluid->particlesColor = NxVec3(1.0f, 0.78f, 0.0f);

    rayFluid->particleTexture = this->fluidTexture;

    NxParticleData particlesData;
    particlesData.numParticlesPtr = rayFluid->nbParticles;
    particlesData.bufferId = &rayFluid->particlesBuffer[0].id;
    particlesData.bufferIdByteStride = sizeof(Particle);
    particlesData.bufferPos = &rayFluid->particlesBuffer[0].position.x;
    particlesData.bufferPosByteStride = sizeof(Particle);
    particlesData.bufferVel = &rayFluid->particlesBuffer[0].velocity.x;
    particlesData.bufferVelByteStride = sizeof(Particle);
    particlesData.bufferLife = &rayFluid->particlesBuffer[0].lifetime;
    particlesData.bufferLifeByteStride = sizeof(Particle);
    particlesData.bufferDensity = &rayFluid->particlesBuffer[0].density;
    particlesData.bufferDensityByteStride = sizeof(Particle);
    particlesData.bufferCollisionNormal = &rayFluid->particlesBuffer[0].collisionNormal.x;
    particlesData.bufferCollisionNormalByteStride = sizeof(Particle);

    //create fluid
    NxFluidDesc fluidDesc;
    fluidDesc.kernelRadiusMultiplier = 2.3f;
    fluidDesc.restParticlesPerMeter = 10.0f;
    fluidDesc.stiffness = 400.0f;
    fluidDesc.viscosity = 400.0f;
    fluidDesc.restDensity = 2000.0f;
    fluidDesc.damping = 0.5f;
    fluidDesc.restitutionForStaticShapes = 0.0f;
    fluidDesc.restitutionForDynamicShapes = 0.0f;
    fluidDesc.simulationMethod = NX_F_SPH;
    fluidDesc.initialParticleData = particlesData;
    fluidDesc.particlesWriteData = particlesData;
    if(PhysxLogic::IsHardwareAccelerated() == false)
        fluidDesc.flags &= ~NX_FF_HARDWARE;
    fluidDesc.flags |= NX_FF_COLLISION_TWOWAY;
    fluidDesc.collisionResponseCoefficient = 0.2f;

    rayFluid->fluid = PhysxLogic::physicsScene->createFluid(fluidDesc);

    //create emitter
    NxFluidEmitterDesc emitterDesc;
    emitterDesc.maxParticles = maxParticles;
    emitterDesc.dimensionX = 0.1f;
    emitterDesc.dimensionY = 0.1f;
    emitterDesc.type = NX_FE_CONSTANT_FLOW_RATE;
    emitterDesc.rate = emitterRate;
    emitterDesc.fluidVelocityMagnitude = 70.0f;
    emitterDesc.particleLifetime = 0.0f;
    emitterDesc.shape = NX_FE_ELLIPSE;  
    emitterDesc.relPose = pose;

    rayFluid->emitter = rayFluid->fluid->createEmitter(emitterDesc);
}

void FluidScene::Update()
{
    for(int i = 0; i < PARTICLES_CONTAINER_COUNT; i++)
    {
        ParticlesContainer *pContainer = &this->differentParticles[i];
        if(pContainer->fluid != NULL)
        {
            if(*pContainer->nbParticles == 0)
                continue;

            for(unsigned int i = 0; i < *pContainer->nbParticles; ++i)
            {
                Particle *particle = &pContainer->particlesBuffer[i];

                particle->color[0] = pContainer->particlesColor.x;
                particle->color[1] = pContainer->particlesColor.y;
                particle->color[2] = pContainer->particlesColor.z;
                particle->color[3] = (pContainer->particlesColorAlpha < 0.0f) ? 0.0001f*particle->density : pContainer->particlesColorAlpha;
            }
        }
    }
}
void FluidScene::Draw()
{
    // Drawing Model
    if(modelActor != NULL)
    {
        float m[16];
        this->modelActor->getGlobalPose().getColumnMajor44(m);
        glPushMatrix();
        glMultMatrixf(m);
        model->Draw();
        glPopMatrix();
    }
    //

    if(groundActor != NULL)
    {
        float m[16];
        this->groundActor->getGlobalPose().getColumnMajor44(m);
        glPushMatrix();
        glMultMatrixf(m);
        glColor3f(1.0f, 1.0f, 1.0f);
        this->groundTexture->Use();
        Renderer::DrawPlane(50.0f, 5, 5);
        glPopMatrix();
    }


    for(int i = 0; i < PARTICLES_CONTAINER_COUNT; i++)
    {
        ParticlesContainer *pContainer = &this->differentParticles[i];
        if(pContainer->fluid != NULL)
        {
            if(*pContainer->nbParticles == 0)
                continue;

            glDepthMask(GL_FALSE);
            glDisable(GL_LIGHTING);
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_POINT_SPRITE_ARB);

            float quadratic[] = {0.0f, 0.0f, 0.01f};
            glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, quadratic);           
            glTexEnvf(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);            

            glPointSize(pContainer->particleSize);

            pContainer->particleTexture->Use();
            glEnableClientState(GL_VERTEX_ARRAY);
            glEnableClientState(GL_COLOR_ARRAY);		
            glColorPointer(4, GL_FLOAT, sizeof(Particle), &pContainer->particlesBuffer[0].color[0]);
            glVertexPointer(3, GL_FLOAT, sizeof(Particle), &pContainer->particlesBuffer[0].position.x);
            glDrawArrays(GL_POINTS, 0, *pContainer->nbParticles);
            glDisableClientState(GL_COLOR_ARRAY);
            glDisableClientState(GL_VERTEX_ARRAY);

            glDisable(GL_POINT_SPRITE_ARB);
            glDisable(GL_BLEND);
            glEnable(GL_LIGHTING);
            glDepthMask(GL_TRUE);
        }
    }		
}

void FluidScene::HandleKeyboardEvent(unsigned char key, int x, int y)
{
    switch(key)
    {
        case '1':
        {
            int maxParticles = 70000;
            if(this->differentParticles[PARTICLES_CONTAINER_WATER].emitter == NULL)
                CreateWaterFountain(maxParticles, modelActor->getGlobalPosition() + NxVec3(0.0f, 5.0f, 0.0f));
            else
                this->differentParticles[PARTICLES_CONTAINER_WATER].ToggleEmitter();

            break;
        }
        case '2':
        {
            if(this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter == NULL)
            {
                NxMat34 fluidStartPose;

                NxVec3 camDir = PhysxLogic::cam->GetDirection();
                camDir.normalize();
                fluidStartPose.t = PhysxLogic::cam->GetPosition() + 1.0f*camDir + NxVec3(0.0f, -0.5f, 0.0f);

                NxMat33 orient;
                NxVec3 emitterDirection(0.0f, 0.0f, 1.0f);
                NxGetUtilLib()->NxFindRotationMatrix(emitterDirection, camDir, orient);  
                fluidStartPose.M = orient;

                int maxParticles = 10000;
                int emitterRate = 100;
                CreateFluidRay(maxParticles, emitterRate, fluidStartPose);
            }
            else
            {
                this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].ToggleEmitter();
            }            

            break;
        }
        case 'r':
        {
            for(int i = 0; i < PARTICLES_CONTAINER_COUNT; i++)
            {
                ParticlesContainer *pContainer = &this->differentParticles[i];
                if(pContainer->fluid != NULL)
                {
                    if(*pContainer->nbParticles != 0)
                        pContainer->fluid->removeAllParticles();
                    if(pContainer->emitter != NULL)
                    {
                        pContainer->fluid->releaseEmitter(*(pContainer->emitter));
                        pContainer->PauseEmitter(false);
                        pContainer->emitter = NULL;
                    }
                }                
            }
            break;
        }
        case 'w': //UP
        {
            NxVec3 dir = PhysxLogic::cam->GetDirection();
            dir.normalize();
            PhysxLogic::cam->MoveBy(dir*1.0);
            if(this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter != NULL)
                this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter->setGlobalPosition(PhysxLogic::cam->GetPosition() + 1.0f*dir + NxVec3(0.0f, -0.5f, 0.0f));
            break;
        }
        case 's': //DOWN
        {
            NxVec3 dir = PhysxLogic::cam->GetDirection();
            dir.normalize();
            PhysxLogic::cam->MoveBy(-dir*1.0);
            if(this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter != NULL)
                this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter->setGlobalPosition(PhysxLogic::cam->GetPosition() + 1.0f*dir + NxVec3(0.0f, -0.5f, 0.0f));
            break;
        }
        case 'd': //RIGHT
        {
            NxVec3 dir = PhysxLogic::cam->GetSideNormal();
            dir.normalize();
            PhysxLogic::cam->MoveBy(dir*1.0);
            if(this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter != NULL)
                this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter->setGlobalPosition(PhysxLogic::cam->GetPosition() + 1.0f*dir + NxVec3(0.0f, -0.5f, 0.0f));
            break;
        }
        case 'a': //LEFT
        {
            NxVec3 dir = PhysxLogic::cam->GetSideNormal();
            dir.normalize();
            PhysxLogic::cam->MoveBy(-dir*1.0);
            if(this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter != NULL)
                this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter->setGlobalPosition(PhysxLogic::cam->GetPosition() + 1.0f*dir + NxVec3(0.0f, -0.5f, 0.0f));
            break;
        }
    }
}

void FluidScene::HandleKeyboardSpecialEvent(int key, int x, int y)
{
    switch(key)
    {
        case GLUT_KEY_F2:
        {
            if(PhysxLogic::IsDebugRenderingEnabled())
            {
                PhysxLogic::SetDebugRenderingEnabled(false);
                PhysxLogic::debugRenderer->DeactivateDebugVisualization(NX_VISUALIZE_FLUID_EMITTERS);
                PhysxLogic::debugRenderer->DeactivateDebugVisualization(NX_VISUALIZE_FLUID_POSITION);
                PhysxLogic::debugRenderer->DeactivateDebugVisualization(NX_VISUALIZE_COLLISION_SHAPES);
                PhysxLogic::debugRenderer->DeactivateDebugVisualization(NX_VISUALIZE_FORCE_FIELDS);
                PhysxLogic::debugRenderer->DeactivateDebugVisualization(NX_VISUALIZE_WORLD_AXES);
            }
            else
            {
                PhysxLogic::SetDebugRenderingEnabled(true);
                PhysxLogic::debugRenderer->ActivateDebugVisualization(NX_VISUALIZE_FLUID_EMITTERS);
                PhysxLogic::debugRenderer->ActivateDebugVisualization(NX_VISUALIZE_FLUID_POSITION);
                PhysxLogic::debugRenderer->ActivateDebugVisualization(NX_VISUALIZE_COLLISION_SHAPES);
                PhysxLogic::debugRenderer->ActivateDebugVisualization(NX_VISUALIZE_FORCE_FIELDS);
                PhysxLogic::debugRenderer->ActivateDebugVisualization(NX_VISUALIZE_WORLD_AXES);
            }
            break;
        }
    }
}


void FluidScene::HandleMouseClickEvent(int button, int state, int x, int y)
{
}

void FluidScene::HandleMousePressedMovementEvent(int x, int y, int dx, int dy)
{
    if(this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter != NULL)
    {
        NxVec3 emitterDirection(0.0f, 0.0f, 1.0f);
        NxVec3 camDir = PhysxLogic::cam->GetDirection();
        camDir.normalize();
        NxMat33 orient;
        NxGetUtilLib()->NxFindRotationMatrix(emitterDirection, camDir, orient);

        this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter->setGlobalOrientation(orient);
        this->differentParticles[PARTICLES_CONTAINER_RAYFLUID].emitter->setGlobalPosition(PhysxLogic::cam->GetPosition() + 1.0f*camDir + NxVec3(0.0f, -0.5f, 0.0f));
    }
}
